package com.kang.c0c.parser;

import com.kang.c0c.util.Context;
import com.kang.c0c.util.LayoutCharacters;
import com.kang.c0c.util.Log;
import com.kang.c0c.util.Position;

/**
 * 
 * Scanner负责识别源语言程序中各类记号
 * 它是由语法分析器调用了Scanner类中的nextToken()来返回Scanner识别出的记号
 * 此类的具体设定与理论实现参见com.kang.c0c.doc中的词法分析 
 *
 * @author kang
 *
 * Feb 5, 2009
 */

public class Scanner implements Tokens, LayoutCharacters {
	//当前缓冲去中的字符
	private char ch;
	
	//当前识别字符的位置信息
	//由col，line作为参数调用com.kang.c0c.util.Position的make(line,col)来产生com.kang.c0c.parser.Token中的pos字段
	private int col;
	private int line;
	
	//前一个字符的位置信息，用于unGetChar()方法中的位置信息的恢复
	private int preLine;
	private int preCol;
	
	//buf为当前缓冲字符区，bp为此缓冲区的扫描指针
	private int bp;
	private char[] buf;
	
	private Log log;
	
	public int errPos;

	public Scanner(Context context) {
		log = Log.instance(context);
		buf = context.buf.toCharArray();
		col = 0;
		line = 1;
		bp = -1;
		errPos = 0;
	}
	
	private void skipComm(){
		bp++;
		
		if((buf[bp] != '/')&&(buf[bp] != '*')){
			bp--;
			return;
		}else if(buf[bp] == '/'){
			while((buf[bp]!='\r')&&(buf[bp]!='\n'))
				bp++;
			col = 0;
			line ++;
			nextChar();
		}else if(buf[bp] == '*'){
			col ++;
			do{
				bp++;
				
				if(buf[bp] == '\r'){
					col = 0;
					line++;
				}else if(buf[bp] == '\n'){
					if(buf[bp-1] != '\r'){
						col = 0;
						line ++;
					}
				}else if(buf[bp] == '\t'){
					col = (col / TabInc * TabInc) + TabInc; 
				}else
					col ++;
			}while(!((buf[bp]=='*')&&(buf[bp+1]=='/')));
			
			bp ++;
			col ++;
			nextChar();
		}
	}
	
	private void nextChar() {
		bp++;
		ch = buf[bp];
		
		preLine = line;
		preCol = col;

		switch (ch) {
		case '\r':
			col = 0;
			line++;
			break;
		case '\n':
			if (bp == 0 | buf[bp - 1] != '\r') {
				col = 0;
				line++;
			}
			break;
		case '\t':
			col = (col / TabInc * TabInc) + TabInc;
			break;
		case '/':
			col ++;
			skipComm();
			break;
		default:
			col++;
			break;
		}
	}
	
	private void unGetChar(){
		line = preLine;
		col = preCol;
		bp--;
		ch = buf[bp];
	}

	/**
	 * Number				::=   ０｜NotZeroNumber
	 * NotZeroNumber		::=  １｜．．．｜９
	 */
	private boolean isdigit(){
		if(ch>='0' && ch<='9')
			return true;
		return false;
	}
	
	private boolean isBadSuffixNum(){
		switch(ch){
		case ' ':
		case '\r':
		case '\t':
		case '\n':
		case ',':
		case ';':
		case ')':
		case '+':
		case '-':
		case '*':
		case '/':
		case '=':
		case '>':
		case '<':
		case '!':
			return false;
		default:
			return true;
		}
	}
	
	/**
	 * Integer				::=  ［＋｜－］NotZeroNumber｛Number｝｜０
	 * 处理带有符号的整数时而是将这个整数分成了符号（‘+’，‘-’）记号和整数这个记号两部分组成，在语义分析时再将他们合并
	 */
	private Token scanNumber() {
		String numStr = "";
		int start = Position.make(line, col);
		
		while(isdigit()){
			numStr += ch;
			nextChar();
		}
		
		if(isBadSuffixNum()){
			if(start != errPos){
				log.error(start, "Bad Suffix Number " + numStr + ch + "...\n"
						+ "\tInvalid identifier name " + numStr + ch + "...");
				log.tips("Have you forgotten some necessary or userful characters likes ',',';' between "
						+ numStr + " and " + ch + "...");
				log.tips("An identifier name must start with character '_' or 'a'~'z' or 'A'~'Z'");
				
				errPos = start;
			}
		}
		
		unGetChar();
		
		int num = 0;
		try{
			num = Integer.parseInt(numStr);
			if((numStr.length() > 1)&&(numStr.charAt(0) == '0'))
				log.warn(start, "Integer ::= NotZeroNumber｛Number｝｜０");
		}catch(NumberFormatException ex){
			if(start != errPos){
				log.error(start, "The literal " + numStr + " of type int is out of range");
				log.tips("The value of type int must between " + Integer.MIN_VALUE + " and " + Integer.MAX_VALUE);
				
				errPos = start;
			}
		}
		
		return new Token(NUM,num,start);
	}

	/**
	 * Character			::=   ＿｜a｜．．．｜z｜A｜．．．｜Z
	 */
	private boolean isalpha(){
		if(ch>='A' && ch<='Z')
			return true;
		else if(ch>='a' && ch<='z')
			return true;
		else if(ch == '_')
			return true;
		return false;
	}
	
	private boolean isalnum(){
		if(isalpha())
			return true;
		else if(isdigit())
			return true;
		return false;
	}
	
	/**
	 * Identifier			::=  Character｛Character｜Number｝
	 */
	private Token scanIdentifier() {
		String ident = "";
		int start = Position.make(line, col);
		
		while(isalnum()){
			ident += ch;
			nextChar();
		}
		unGetChar();
		
		String identLow = ident.toLowerCase();
		
		if(identLow.equals("const"))
			return new Token(CONST,null,start);
		else if(identLow.equals("int"))
			return new Token(INT,null,start);
		else if(identLow.equals("if"))
			return new Token(IF,null,start);
		else if(identLow.equals("else"))
			return new Token(ELSE,null,start);
		else if(identLow.equals("while"))
			return new Token(WHILE,null,start);
		else if(identLow.equals("void"))
			return new Token(VOID,null,start);
		else if(identLow.equals("main"))
			return new Token(MAIN,null,start);
		else if(identLow.equals("scanf"))
			return new Token(SCANF,null,start);
		else if(identLow.equals("printf"))
			return new Token(PRINTF,null,start);
		else if(identLow.equals("return"))
			return new Token(RETURN,null,start);
		
		if(ident.length() > 20)
			log.warn(start, "Identifier '" + ident + "' is too long!");
		
		return new Token(IDENTIFIER,ident,start);
	}
	
	/*
	 * 字符串中的转义字符的转化，主要转化可见字符中的转义字符，其余转义字符一律被当作非法转义字符处理
	 */
	private char convertUnicode(){
		switch(ch){
		case 'b':
			
		case 't':
			
		case 'n':
			
		case 'f':
			
		case 'r':
			
		case '"':
		
		case '\'':
			
		case '\\':
			return ch;
		default:
			log.warn(Position.make(preLine, preCol),"Invalid escape sequence (valid ones are \b \t \n \f \r \" \' \\ )");
			return ch;
		}
	}
	
	/**
	 * String				::=   "｛<合法字符>}" //字符串中可以出现所有合法的可打印字符集中的字符
	 */
	private Token scanString(){
		String str = "";
		int start = Position.make(line, col);
		
		nextChar();
		while(ch != '"' && ch!='\r' && ch!='\n'){
			str += ch;
			if(ch == '\\'){
				nextChar();
				str += convertUnicode();
			}
				
			nextChar();
		}
		
		if(ch != '"'){
			if(start != errPos){
				log.error(start, "Not close the string");
				errPos = start;
			}
		}

		return new Token(STRING,str,start);
	}
	
	
	public Token nextToken() {
		nextChar();
		while (true) {
			switch (ch) {
			case FF:
			case LF:
			case TAB:
			case CR:
			case ' ':
			case '\t':
				nextChar();
				break;
			case '\0':
				return new Token(EOF,null,Position.make(line, col));
			case 'A':
			case 'B':
			case 'C':
			case 'D':
			case 'E':
			case 'F':
			case 'G':
			case 'H':
			case 'I':
			case 'J':
			case 'K':
			case 'L':
			case 'M':
			case 'N':
			case 'O':
			case 'P':
			case 'Q':
			case 'R':
			case 'S':
			case 'T':
			case 'U':
			case 'V':
			case 'W':
			case 'X':
			case 'Y':
			case 'Z':
			case 'a':
			case 'b':
			case 'c':
			case 'd':
			case 'e':
			case 'f':
			case 'g':
			case 'h':
			case 'i':
			case 'j':
			case 'k':
			case 'l':
			case 'm':
			case 'n':
			case 'o':
			case 'p':
			case 'q':
			case 'r':
			case 's':
			case 't':
			case 'u':
			case 'v':
			case 'w':
			case 'x':
			case 'y':
			case 'z':
			case '_':
				return scanIdentifier();
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				return scanNumber();
			case '"':
				return scanString();
			case '+':
				return new Token(PLUS,null,Position.make(line,col));
			case '-':
				return new Token(SUB,null,Position.make(line,col));
			case '*':
				return new Token(STAR,null,Position.make(line,col));
			case '/':
				return new Token(SLASH,null,Position.make(line,col));
			case '=':
				nextChar();
				if(ch == '=')
					return new Token(EQEQ,null,Position.make(preLine,preCol));
				unGetChar();
				return new Token(EQ,null,Position.make(line,col));
			case '>':
				nextChar();
				if(ch == '=')
					return new Token(GTEQ,null,Position.make(preLine,preCol));
				unGetChar();
				return new Token(GT,null,Position.make(line,col));
			case '<':
				nextChar();
				if(ch == '=')
					return new Token(LTEQ,null,Position.make(preLine,preCol));
				unGetChar();
				return new Token(LT,null,Position.make(line,col));
			case '!':
				nextChar();
				if(ch != '='){
					unGetChar();
					if(Position.make(line, col) != errPos){
						log.error(Position.make(line, col), "Unrecognized character '!'");
						log.tips("Do you want to input \"!=\" ");
						
						errPos = Position.make(line, col);
					}
				}
				return new Token(BANGEQ,null,Position.make(preLine,preCol));
			case '(':
				return new Token(LPAREN,null,Position.make(line,col));
			case ')':
				return new Token(RPAREN,null,Position.make(line,col));
			case '{':
				return new Token(LBRACKET,null,Position.make(line,col));
			case '}':
				return new Token(RBRACKET,null,Position.make(line,col));
			case ';':
				return new Token(SEMI,null,Position.make(line,col));
			case ',':
				return new Token(COMMA,null,Position.make(line,col));
			default:
				if(Position.make(line, col) != errPos){
					log.error(Position.make(line, col), "Unrecognized character '" + ch + "'");
					errPos = Position.make(line, col);
				}
				nextChar();
				break;
			}
		}
	}
	
	public String dumpToken(Token token){
		switch(token.getToken()){
		case EOF:
			return "<EOF>";
		case ERROR:
			return "<ERROR>";
		case IDENTIFIER:
				return "<identifier>'" + token.getValue() +"'";
		case NUM:
			return "<number>'" + token.getValue() + "'";
		case STRING:
			return "<string>'" + token.getValue() + "'";
		case  CONST:
			return "'const'";
		case INT:
			return "'int'";
		case IF:
			return "'if'";
		case ELSE:
			return "'else'";
		case WHILE:
			return "'while'";
		case VOID:
			return "'void'";
		case MAIN:
			return "'main'";
		case SCANF:
			return "'scanf'";
		case PRINTF:
			return "'printf'";
		case RETURN:
			return "'return'";
		case PLUS:
			return "'+'";
		case SUB:
			return "'-'";
		case STAR:
			return "'*'";
		case SLASH:
			return "'/'";
		case EQ:
			return "'='";
		case GT:
			return "'>'";
		case LT:
			return "'<'";
		case EQEQ:
			return "'=='";
		case LTEQ:
			return "'<='";
		case GTEQ:
			return "'>='";
		case BANGEQ:
			return "'!='";
		case LPAREN:
			return "'('";
		case RPAREN:
			return "')'";
		case LBRACKET:
			return "'{'";
		case RBRACKET:
			return "'}'";
		case SEMI:
			return ";'";
		case COMMA:
			return "','";
		default:
			return "<UNKNOWTOKEN>";	
		}
	}
}